package com.kai.kaispringmvc.servlet;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.kai.kaispringmvc.annotation.kaiController;
import com.kai.kaispringmvc.annotation.kaiRequestMapping;
import com.kai.kaispringmvc.annotation.kaiRequestParam;
import com.kai.kaispringmvc.annotation.kaiResponseBody;
import com.kai.kaispringmvc.context.kaiwebApplicationContext;
import com.kai.kaispringmvc.handler.kaiHandler;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author kai
 * @version 1.0
 * @date 2024/7/10
 * @Description
 */
/*
  kaiDispatcherServlet充当原生Dispatcherservlet
  本质是一个servlet，继承httpServlet
  设计servlet基础
* */
public class kaiDispatcherServlet extends HttpServlet {

    //定义属性 handlerList，保存kaihandler[url 和控制器方法的映射关系]
    private List<kaiHandler> handlerList = new ArrayList<>();


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);

    }

    kaiwebApplicationContext context = null;

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

        String configLocation = servletConfig.getInitParameter("ContextConfigLocation");


        context = new kaiwebApplicationContext(configLocation);
        context.init();

        //调用
        initHandlermapping();

        //输出handlerList
        System.out.println("handlerList=" + handlerList);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //调用方法。完成分发请求
        executeDispatch(req, resp);
    }

    //编写方法完成url和控制器方法的映射
    private void initHandlermapping() {

        if (context.ioc.isEmpty()) {
            return;
        }

        //遍历IOC容器bean对象，然后进行url映射处理
        for (Map.Entry<String, Object> entry : context.ioc.entrySet()) {

            //先取出注入的object的clazz对象
            Class<?> clazz = entry.getValue().getClass();

            //如果注入的是kaiController
            if (clazz.isAnnotationPresent(kaiController.class)) {

                //取出他所有方法
                Method[] declaredMethods = clazz.getDeclaredMethods();

                //遍历方法
                for (Method method : declaredMethods) {
                    //判断是否有@requestmapping注解
                    if (method.isAnnotationPresent(kaiRequestMapping.class)) {
                        //取出@requestmapping的value值-->路径
                        kaiRequestMapping annotation = method.getAnnotation(kaiRequestMapping.class);

                        String value = annotation.value();

                        //创建kaiHandler对象-->就是一个映射关系
                        kaiHandler kaiHandler = new kaiHandler(value, entry.getValue(), method);

                        //放入到handlerList
                        handlerList.add(kaiHandler);

                    }


                }

            }

        }


    }


    //编写方法，通过request对象，返回kaiHandler
    //如果没有，返回null，返回404
    private kaiHandler getkaihandler(HttpServletRequest req) {

        //1.先获取用户请求的url
        //注意工程路径问题
        //两种方法解决
        String reqRequestURI = req.getRequestURI();

        //遍历handlerList
        for (kaiHandler kaiHandler : handlerList) {
            if (reqRequestURI.equals(kaiHandler.getUrl())) {
                //说明匹配上了
                return kaiHandler;

            }

        }


        return null;
    }


    //编写方法，完成分发请求任务
    private void executeDispatch(HttpServletRequest req, HttpServletResponse resp) {

        kaiHandler getkaihandler = getkaihandler(req);
        try {
            if (null == getkaihandler) {// 404

                resp.getWriter().print("<h1>404 not found</h1>");


            } else {

                //得到目标方法所有的形参参数信息【对应的数组】
                Class<?>[] parameterTypes = getkaihandler.getMethod().getParameterTypes();

                //创建一个参数数组【对应实参数组】
                Object[] params = new Object[parameterTypes.length];

                //遍历parameterTypes形参数组，根据形参数组信息，将实参填充到形参数组中
                for (int i = 0; i < parameterTypes.length; i++) {
                    //取出每一个形参类型
                    Class<?> parameterType = parameterTypes[i];
                    //如果这个形参是HttpServletRequest，将request填充到params
                    //在原生SpringMvc中，是按照类型进行匹配，此处简化使用名字匹配
                    if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
                        params[i] = req;
                    } else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
                        params[i] = resp;

                    }

                }

                //将http请求参数封装到params数组里，要注意填充实参的时候的顺序问题
                //1.获取http请求的参数集合
                //   http://example.com/?colors=red&colors=blue&colors=green
                //   返回的Map<String,String[]>String:表示http请求的参数名
                // 返回数组的原因请看链接
                //这里处理中文乱码平台
                req.setCharacterEncoding("utf-8");
                Map<String, String[]> parameterMap = req.getParameterMap();

                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {

                    //对应请求的参数名
                    String name = entry.getKey();

                    //这里简化，这里考虑参数是单值的情况，不考虑类似checkbox提交的数据
                    String value = entry.getValue()[0];

                    //我们得到请求的参数对应目标方法的第几个参数，然后将其填充
                    //这里专门编写一个方法，得到请求的参数对应的是第几个形参

                    int indexRequestParamrterIndex = getIndexRequestParamrterIndex(getkaihandler.getMethod(), name);

                    if (indexRequestParamrterIndex != -1) {
                        params[indexRequestParamrterIndex] = value;
                    } else {  //说明并没有找到

                        //先得到目标方法的所有形参的名称
                        //进行遍历，如果匹配就把当前请求的参数值填充到param数组

                        List<String> parameterNames = getparameterName(getkaihandler.getMethod());

                        for (int i = 0; i < parameterNames.size(); i++) {
                            //如果请求参数名和目标方法的参数名一样，说明匹配成功
                            if (name.equals(parameterNames.get(i))) {
                                params[i] = value;
                                break;

                            }


                        }

                    }

                }


                /*此处目前是硬编码，应该将需要传递给目标方法的实参 ==》封装到参数数组然后以反射调用的方式传递给目标方法*/
                // getkaihandler.getMethod().invoke(getkaihandler.getController(),req,resp);
                /*经过封装数组，此时invoke内传入封装数组即可*/
                Object result = getkaihandler.getMethod().invoke(getkaihandler.getController(), params);

                //这里对返回的结果进行解析=》原生springmvc 可以通过视图解析器来完成
                // 这里直接解析
                //首先判断resul是否是string类型
                if (result instanceof String) {

                    String viewName = (String) result;
                    if (viewName.contains(":")) { //说明返回带路径，还得扩展

                        String viewType = viewName.split(":")[0];
                        /*在Java中，viewName.split(":")[0] 这段代码执行了以下操作：
                        split(":"): 这个方法是字符串 viewName 的一个实例方法调用，它使用正则表达式 ":"（一个冒号字符）作为分隔符来分割字符串。结果是一个字符串数组，其中每个元素都是 viewName 中由冒号分隔的部分。
                        [0]: 这是对上述生成的字符串数组的第一个元素的访问。数组索引从0开始，所以 [0] 表示第一个元素。这意味着这段代码返回的是 viewName 字符串被冒号分割后的第一部分。
                        例如，如果 viewName 是 "main:detail"，那么 viewName.split(":")[0] 将返回 "main"。*/

                        /*生成的字符串数组的第二个元素的访问*/
                        String viewPages = viewName.split(":")[1];  //forward或者redirect

                        if ("forward".equals(viewType)) { //说明想要请求转发

                            req.getRequestDispatcher(viewPages).forward(req,resp);

                        } else if ("redirect".equals(viewType)) {
                            resp.sendRedirect(viewPages);

                        } else { //默认请求转发
                            req.getRequestDispatcher(viewName).forward(req, resp);

                        }


                    }

                } else if (result instanceof ArrayList) {
                    if (getkaihandler.getMethod().isAnnotationPresent(kaiResponseBody.class)) {
                        //把result ArrayList转成json格式数据 ===》然后返回
                        //这里我们使用jackson包下的工具类

                        ObjectMapper objectMapper = new ObjectMapper();
                        String resultjson = objectMapper.writeValueAsString(result);

                        //返回数据

                        resp.setContentType("text/html;charset=utf-8");

                        PrintWriter writer = resp.getWriter();
                        writer.write(resultjson);
                        writer.flush();
                        writer.close();
                    }





                }



            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    //编写方法，返回请求的参数是目标方法的第几个形参

    /**
     * @param method 目标方法
     * @param name   对应形参
     * @return 是目标方法第几个形参//
     */
    public int getIndexRequestParamrterIndex(Method method, String name) {

        //先得到method的所有参数
        Parameter[] parameters = method.getParameters();

        for (int i = 0; i < parameters.length; i++) {
            //取出当前参数
            Parameter parameter = parameters[i];

            //判断parameter前是否有RequestParam注解
            if (parameter.isAnnotationPresent(kaiRequestParam.class)) {

                //取出注解内的value值
                kaiRequestParam RequestParamannotation = parameter.getAnnotation(kaiRequestParam.class);
                //匹配比较
                if (name.equals(RequestParamannotation.value())) {
                    return i; //找到返回
                }

            }


        }

        //如果没有匹配到，返回-1
        return -1;
    }

    /*编写方法，得到目标方法的所有形参的名称，并放入到集合中返回*/

    /**
     *
     * @param method 目标方法
     * @return 所有形参的名称，并放入集合中返回
     */
    public List<String> getparameterName(Method method) {

        List<String> parametersList = new ArrayList<>();

        //获取到所有的参数名
        Parameter[] methodParameters = method.getParameters();

        //methodParameter.getName()得到的是：目标方法的形参参数列表=[arg0, arg1, arg2]
        //添加插件，利用java8特性解决
        //遍历methodParameters放入parametersList
        for (Parameter methodParameter : methodParameters) {
            parametersList.add(methodParameter.getName());

        }
        System.out.println("目标方法的形参参数列表=" + parametersList);
        return parametersList;

    }


}
